#include "LZW.h"

LZW::LZW(const char *fileNameSource, const char *fileNameOutput)
{
    fileSource = fopen(fileNameSource, "rb");
    psourSize = get_file_size(fileSource);
    psour = new unsigned char[psourSize];
    psourSize = (int)fread(psour, 1, psourSize, fileSource);
    fclose(fileSource);
    this->fileNameInput = fileNameSource;
    this->fileNameOutput = fileNameOutput;

	dic = new LZWDictionary;
}

LZW::~LZW()
{
	delete psour;
	delete dic;
}

int LZW::get_file_size(FILE *fp)
{
	fseek(fp, 0, SEEK_END);
	int size = ftell(fp);
	fseek(fp, 0, SEEK_SET);
	return size;
}

void LZW::compress()
{
	vector<unsigned char> seq, seqPrevious;
	unsigned short bcode, bcode2;
	unsigned char cNow, cNext;

	for(unsigned int i = 0; i < psourSize; ++i)
	{
		cNow = psour[i];
		seq = vector<unsigned char> (1, cNow);
		bcode = dic->getBitCode(seq);

		// Tentar ver se a seq + prox char pertence ao dic
		while(true)
		{
			seq.push_back(psour[i + 1]);
#ifdef __DEBUG__
			cout << "seq == " ;
			for(unsigned int j = 0; j < seq.size(); ++j)
				cout << (char)seq[j];
			cout << endl;
#endif
			bcode2 = dic->getBitCode(seq);
			if(bcode2 == 65535)
			{
#ifdef __DEBUG__
				cout << "(" << bcode << ")";
				vector<unsigned char> word = dic->getWord(bcode);
				for(unsigned int j = 0; j < word.size(); ++j)
					cout << (char)word[j];
				cout << endl;
#endif
				bs.write(bcode);
				if(seqPrevious.size() != 0)
					dic->addWord(seqPrevious);

				seqPrevious = seq;
				break;
			}
			bcode = bcode2;
			++i;
		}
	}

	// Guardar no ficheiro
	store();
}

void LZW::store()
{
    ofstream* file = new ofstream();
    file->open(fileNameOutput, ios::out | ios::binary);
    file->write(new char(psourSize >> 24), 1);
    file->write(new char(psourSize >> 16), 1);
    file->write(new char(psourSize >> 8), 1);
    file->write(new char(psourSize), 1);
	bs.store(file);
	file->close();
}

void LZW::decompress()
{
	load();
	char * pout = new char[256 * 256];
	unsigned int poutOffset = 0, fileOffset = 0;
	FILE * pFile = fopen( fileNameOutput, "wb" );
	unsigned short cBitCodePrevious = bs.getBitCode(12), cBitCodeNow;

	vector<unsigned char> seq;

	while(!bs.eos())
	{
		seq = dic->getWord(cBitCodePrevious);

		try
		{
			cBitCodeNow = bs.getBitCode(12);
		}
		catch(BitStreamInsufficientBits *bsib)
		{
			break;
		}
#ifdef __DEBUG__
		cout << "(" << cBitCodePrevious << ")";
		for(unsigned int j = 0; j < seq.size(); ++j)
			cout << (char)seq[j];
		cout << " " << endl;
#endif

		for(unsigned int i = 0; i < seq.size(); ++i)
		{
			pout[poutOffset++] = seq[i];
			if(poutOffset >= 256 * 256)
			{
				fwrite(pout, 1, 256 * 256, pFile);
				poutOffset = 0;
			}
			++fileOffset;
		}
		if(poutSize == fileOffset)
			break;

		seq.push_back(dic->getWord(cBitCodeNow)[0]);

		dic->addWord(seq);
		cBitCodePrevious = cBitCodeNow;
	}
	// Escrever ultima string
	seq = dic->getWord(cBitCodePrevious);
	for(unsigned int i = 0; i < seq.size(); ++i)
	{
		pout[poutOffset++] = seq[i];
		if(poutOffset >= 256 * 256)
		{
			fwrite(pout, 1, 256 * 256, pFile);
			poutOffset = 0;
		}
		++fileOffset;
	}

	fwrite(pout, 1 , poutOffset, pFile);
	fclose(pFile);
	if(poutSize == fileOffset)
		cout << "Provavelmente a string esta certa." << endl;
	else
		cout << "Provavelmente a string esta errada." << endl;
}

void LZW::load()
{
    fileSource = fopen(fileNameInput, "rb");
    psourSize = get_file_size(fileSource) - 4;
    psour = new unsigned char[psourSize];
    unsigned char * csize = new unsigned char[4];
    (int)fread(csize, 1, 4, fileSource);
	poutSize = (((unsigned char)csize[0]) << 24)
			 + (((unsigned char)csize[1]) << 16)
			 + (((unsigned char)csize[2]) << 8)
			 +  ((unsigned char)csize[3]);

    psourSize = (int)fread(psour, 1, psourSize, fileSource);
	fclose(fileSource);

	for(unsigned int i = 0; i < psourSize; ++i)
		bs.write(psour[i]);
}
